Skip to content

<doc>[sdnController]: integraion zns into cloud#3711

Open
MatheMatrix wants to merge 18 commits into5.5.12from
sync/shixin.ruan/shixin.ruan-zcf-2047@@2
Open

<doc>[sdnController]: integraion zns into cloud#3711
MatheMatrix wants to merge 18 commits into5.5.12from
sync/shixin.ruan/shixin.ruan-zcf-2047@@2

Conversation

@MatheMatrix
Copy link
Copy Markdown
Owner

Resolves: ZCF-1365

Change-Id: I73687962636e7871626e687761626d6661716668

sync from gitlab !9574

Resolves: ZCF-1365

Change-Id: I73687962636e7871626e687761626d6661716668
Resolves: ZCF-1365

Change-Id: I7262787a6474667a766d77766165796f73717775
Resolves: ZCF-1365

Change-Id: I647469616679716d7366686c77617073746f776c
Resolves: ZCF-1365

Change-Id: I7a61747778757574656967626c6a736366716b6a
<doc>[sdnController]: integraion zns into cloud

See merge request zstackio/zstack!9447
Resolves: ZCF-1365

Change-Id: I65767164636167726d6369726f63666b68666477
<fix>[zns]: add ZnsUuidHelper utility for UUID format conversion

See merge request zstackio/zstack!9467
Resolves: ZCF-1365

Change-Id: I696f6a6a6e6d7867746e70736d6d646861686166
<fix>[rest]: fix bug<description>

See merge request zstackio/zstack!9500
Resolves: ZCF-1365

Change-Id: I73637569786c6d6e6d6479646961726365737074
<fix>[rest]: improve markdown validation error reporting

See merge request zstackio/zstack!9529
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

引入 ZNS/Geneve 支持与多处 NIC 生命周期改造:新增异步 Webhook 回调客户端与多项扩展点,重构 VM NIC 分配/释放流程以支持 Before/After/Release SDN 扩展、更新 KVM/NIC 完成逻辑、添加数据库表与错误码,并补充文档与测试工具。

Changes

Cohort / File(s) Summary
VM NIC 分配重构
compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java
重构 NIC 分配为私有 helper(MAC/构建 Nic/持久化静态 IP);提前创建 VmNicVO 并在持久化前调用 BeforeAllocateVmNicExtensionPoint(异步 Completion);重复 MAC/无类型错误收集后统一处理。
新增 SDN NIC 分配流程
compute/src/main/java/org/zstack/compute/vm/VmAllocateSdnNicFlow.java, compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
新增 VmAllocateSdnNicFlow,插入 NIC 附加链,按扩展异步调用 AfterAllocateSdnNicExtensionPointafterAllocateSdnNic/rollbackSdnNic/releaseSdnNics 并聚合错误决定流向。
NIC 释放/Detach 流程调整
compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java, compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java
将 SDN 资源释放委派至 callReleaseSdnNics(...);收集待删 VO,先调用扩展释放并记录警告,扩展完成后再删除 VO 并推进链路。
SDN 控制器工厂/管理改动
plugin/sdnController/.../SdnControllerFactory.java, plugin/sdnController/.../SdnControllerManagerImpl.java, plugin/sdnController/.../SdnControllerBase.java, plugin/sdnController/.../H3cVcfcSdnControllerFactory.java, plugin/sugonSdnController/.../SugonSdnControllerFactory.java
移除接口方法 persistSdnController 及实现;SdnControllerManagerImplAfterAllocateSdnNicExtensionPoint 替代多处 NIC 生命周期扩展点,并改用控制器实例方法删除 DB 记录;部分异常改为记录警告。
新增/调整扩展点
header/src/main/java/org/zstack/header/vm/..., header/src/main/java/org/zstack/header/network/l3/AfterSetL3NetworkMtuExtensionPoint.java
新增/调整扩展点:BeforeAllocateVmNicExtensionPointAfterAllocateSdnNicExtensionPoint(3 方法)、AfterAllocateVmNicIpExtensionPointAfterReleaseVmNicExtensionPointAfterSetL3NetworkMtuExtensionPoint,均采用异步 Completion 协议。
L2/L3 类型与常量
header/src/main/java/org/zstack/header/network/l2/L2NetworkConstant.java, header/src/main/java/org/zstack/header/network/l3/L3NetworkType.java, .../L3NetworkInventory.java, .../L3NetworkVO.java
新增 L2GeneveNetworkVSWITCH_TYPE_ZNS、加速类型常量(vDPAdpdkvhostuserclient);L3NetworkType 增加 ipAddressAllocationEnabled 属性,非 basic 类型按枚举判断 IP 分配使能。
Vm 系统标签与常量迁移
compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java, header/src/main/java/org/zstack/header/vm/VmOvsNicConstant.java
新增 NIC 级别系统标签 IFACE_ID;移除 VmOvsNicConstant(将加速常量迁移入 L2 常量)。
KVM/NIC 完成与 NicTO 扩展
plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java, plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java, plugin/kvm/.../KVMRealizeL2*.java, plugin/kvm/.../KVMHostFactory.java
NicTO 新增 bridgePortTypeinterfaceId;KVM 完成逻辑对 ZNS 强制 br-int/openvswitch 并从 VmSystemTags.IFACE_ID 填充 interfaceIdsrcPath 判定由 L2 vSwitch 类型改为 NIC 加速类型。
Webhook 回调框架
core/src/main/java/org/zstack/core/rest/webhook/WebhookProtocol.java, core/src/main/java/org/zstack/core/rest/webhook/WebhookCallbackClient.java
新增通用 Webhook 协议接口与回调客户端,支持注册回调路径、submit/超时/并发映射、回调分发与外部 deliverCallback。
数据库模式与错误码
conf/db/upgrade/V5.5.18__schema.sql, utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java
新增三张 ZNS 相关表(ZnsControllerVOL2GeneveNetworkVOZnsTransportZoneVO,带外键 ON DELETE CASCADE 与时间戳);新增若干 L2/ZNS 错误码常量。
文档、测试与生成器
docs/modules/network/.../ZnsIntegration.adoc, ZStackL2NetworkType.adoc, nav.adoc, networkResource.adoc, testlib/.../ApiHelper.groovy, testlib/.../SdnControllerSpec.groovy, rest/.../RestDocumentationGenerator.groovy
新增 ZNS 集成与 L2 三层模型文档并更新导航;ApiHelper 新增 createL2GeneveNetworkSdnControllerSpec 使用 JSONObjectUtil.rehashObject 恢复 inventory;文档生成脚本改为聚合一致性校验并抛出汇总错误。
网络校验与 L2 行为调整
network/src/main/java/org/zstack/network/l2/...
针对 ZNS 的 attach/validate 跳过物理接口冲突与主机状态限制,部分 SQL 排除 ZNS 类型;新增 beforeUpdate 扩展调用并改进异常处理与空集合处理。
其他小改动
compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java, plugin/kvm/.../KVMHostFactory.java
修正条件判定以避免 NPE、拼写修正、移除未使用导入、若干日志/异常处理与格式调整。

Sequence Diagram(s)

sequenceDiagram
    participant VM as VM 创建流程
    participant NICF as VmAllocateNicFlow
    participant DB as 数据库 (vnicFactory/VO)
    participant BEFORE as BeforeAllocateVmNic<br/>扩展
    participant SDN as VmAllocateSdnNicFlow
    participant AFTER as AfterAllocateSdnNic<br/>扩展

    VM->>NICF: 运行分配 NIC 流
    NICF->>NICF: allocateMac() / buildNicInventory()
    NICF->>DB: vnicFactory.createVmNic()
    DB-->>NICF: 返回 VmNicVO
    NICF->>BEFORE: callBeforeAllocateVmNicExtensions(nic, spec)
    BEFORE-->>NICF: 扩展完成 (Completion)
    NICF->>DB: persistStaticIpIfNeeded() / update VO
    NICF-->>VM: NIC 分配完成
    VM->>SDN: 触发 VmAllocateSdnNicFlow
    SDN->>SDN: 查询 AfterAllocateSdnNic 扩展列表
    loop 每个扩展
      SDN->>AFTER: afterAllocateSdnNic(spec, nics)
      AFTER-->>SDN: success / fail
    end
    SDN-->>VM: 根据错误聚合决定 next 或 fail
Loading
sequenceDiagram
    participant DET as VmDetachNicFlow / VmReturnReleaseNicFlow
    participant BUS as IP 回收总线
    participant SDN as callReleaseSdnNics
    participant EXT as AfterAllocateSdnNic<br/>扩展
    participant DB as 数据库 (删除 VO)

    DET->>BUS: return used IPs
    DET->>SDN: callReleaseSdnNics(nics, completion)
    SDN->>SDN: 查询扩展列表并循环调用
    loop 每个扩展
      SDN->>EXT: releaseSdnNics(nics)
      EXT-->>SDN: 完成(错误记录为 warn)
    end
    SDN->>DB: 删除 queued nicsToDelete
    DB-->>DET: 删除完成
    DET->>流程: trigger.next() / chain.next()
Loading
sequenceDiagram
    participant Client as 外部系统
    participant WC as WebhookCallbackClient
    participant MAP as 任务注册表 (ConcurrentHashMap)
    participant TIME as 超时调度器
    participant H as HTTP 回调接收点

    Client->>WC: submit(completion, timeout)
    WC->>MAP: 存储 taskId -> completion
    WC->>TIME: 安排超时任务
    WC-->>Client: 返回 taskId
    Client->>外部: 执行异步操作
    H->>WC: deliverCallback(payload 包含 taskId)
    WC->>MAP: 移除并取出 completion
    WC->>TIME: 取消超时
    WC-->>completion: success / fail
    alt 超时触发
      TIME->>WC: 调用 fail(taskId, error)
      WC->>MAP: 移除并完成失败
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐰 春泥软踩,线缆细如丝,

我把 br-int 为你架成桥,
回调携 taskId 来敲门,
扩展点串起一片网,
小兔欢跳,ZNS 花正开。

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.10% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR标题遵循[scope]: 的格式,长度为47字符,在72字符限制内。
Description check ✅ Passed PR描述包含工作单号ZCF-1365、变更ID和来源说明,与提供的原始摘要中的ZNS集成变更相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/shixin.ruan/shixin.ruan-zcf-2047@@2

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.42.1)
utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (4)
header/src/main/java/org/zstack/header/vm/AfterReleaseVmNicExtensionPoint.java (1)

6-11: 建议统一“Release”与“Deleted”语义,避免生命周期阶段歧义。

接口/方法名强调 Release,但注释写的是“deleted from database”;建议统一术语或按真实触发时机调整命名/注释,避免扩展点挂错阶段。

Based on learnings, 在 ZStack 中扩展点通常按不同执行阶段区分,命名和触发语义需要严格对齐。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@header/src/main/java/org/zstack/header/vm/AfterReleaseVmNicExtensionPoint.java`
around lines 6 - 11, The interface AfterReleaseVmNicExtensionPoint and its
method afterReleaseVmNic currently mix "Release" in the name with "deleted from
database" in the Javadoc; update either the name or the comment so lifecycle
semantics match: either rename the interface/method to
DeletedVmNicExtensionPoint/afterDeletedVmNic if it is invoked after DB deletion,
or change the Javadoc to state the exact "release" stage (e.g., resource
release/cleanup before/after DB deletion) if the name is correct; make the
change where AfterReleaseVmNicExtensionPoint and afterReleaseVmNic are declared
and ensure VmNicInventory references and any callers/registrations are updated
consistently to avoid mismatched lifecycle semantics.
testlib/src/main/java/org/zstack/testlib/SdnControllerSpec.groovy (1)

62-64: 建议先校验查询结果再取首元素。

Line [62]-Line [64] 直接取 [0],查询为空时会在 postCreate 阶段抛异常。建议先判空并给出明确错误信息。

可参考的修改
         postCreate {
-            inventory = JSONObjectUtil.rehashObject(querySdnController {
-                conditions=["uuid=${inventory.uuid}".toString()]
-            }[0], SdnControllerInventory.class)
+            def controllers = querySdnController {
+                conditions = ["uuid=${inventory.uuid}".toString()]
+            }
+            if (!controllers) {
+                throw new IllegalStateException("Failed to query created SDN controller by uuid: ${inventory.uuid}")
+            }
+            inventory = JSONObjectUtil.rehashObject(controllers[0], SdnControllerInventory.class)
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testlib/src/main/java/org/zstack/testlib/SdnControllerSpec.groovy` around
lines 62 - 64, 在使用 JSONObjectUtil.rehashObject(querySdnController {
conditions=["uuid=${inventory.uuid}".toString()] }[0],
SdnControllerInventory.class) 之前不要直接取索引 0;在 SdnControllerSpec 的 postCreate
流程里先检查 querySdnController 返回的结果是否为 null 或为空列表(例如检查变量或返回值的
size/empty),若为空则抛出或记录一个明确的错误(包含 inventory.uuid 和上下文信息),否则再取第一项并传入
JSONObjectUtil.rehashObject 转换为 SdnControllerInventory。
header/src/main/java/org/zstack/header/network/l3/L3NetworkVO.java (1)

118-120: 建议抽取公共判定逻辑,避免 VO/Inventory 后续漂移。

这里与 L3NetworkInventory.enableIpAddressAllocation() 的分支几乎一致,建议下沉到 L3NetworkType 的静态工具方法统一复用。

♻️ 参考改法
*** header/src/main/java/org/zstack/header/network/l3/L3NetworkType.java
@@
+    public static boolean isIpAddressAllocationEnabled(String typeName) {
+        if (!hasType(typeName)) {
+            return true;
+        }
+        return valueOf(typeName).isIpAddressAllocationEnabled();
+    }

*** header/src/main/java/org/zstack/header/network/l3/L3NetworkInventory.java
@@
-            if (L3NetworkType.hasType(getType())) {
-                return L3NetworkType.valueOf(getType()).isIpAddressAllocationEnabled();
-            }
-            return true;
+            return L3NetworkType.isIpAddressAllocationEnabled(getType());

*** header/src/main/java/org/zstack/header/network/l3/L3NetworkVO.java
@@
-            if (L3NetworkType.hasType(getType())) {
-                return L3NetworkType.valueOf(getType()).isIpAddressAllocationEnabled();
-            }
-            return true;
+            return L3NetworkType.isIpAddressAllocationEnabled(getType());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@header/src/main/java/org/zstack/header/network/l3/L3NetworkVO.java` around
lines 118 - 120, Extract the repeated type-checking logic into a single static
helper on L3NetworkType and call it from both L3NetworkVO and
L3NetworkInventory.enableIpAddressAllocation(): move the if
(L3NetworkType.hasType(getType())) { return
L3NetworkType.valueOf(getType()).isIpAddressAllocationEnabled(); } logic into a
new L3NetworkType.static method (e.g., isIpAddressAllocationEnabled(String type)
or isIpAddressAllocationEnabledFor(String type)) that performs the hasType check
and returns the flag, then replace the existing branches in L3NetworkVO and
L3NetworkInventory.enableIpAddressAllocation() to delegate to that new static
method to avoid duplication and future drift.
plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java (1)

4183-4184: 将 ZNS 端口常量提取,避免魔法值散落

"br-int""openvswitch" 建议提取为类常量(或统一常量类),减少拼写漂移风险并提升可维护性。

♻️ 建议修改
 public class KVMHost extends HostBase implements Host {
     private static final CLogger logger = Utils.getLogger(KVMHost.class);
     private static final ZTester tester = Utils.getTester();
+    private static final String ZNS_BRIDGE_NAME = "br-int";
+    private static final String ZNS_BRIDGE_PORT_TYPE = "openvswitch";
-            to.setBridgeName("br-int");
-            to.setBridgePortType("openvswitch");
+            to.setBridgeName(ZNS_BRIDGE_NAME);
+            to.setBridgePortType(ZNS_BRIDGE_PORT_TYPE);

As per coding guidelines: 避免使用魔法值(Magic Value),应替换为常量或枚举。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java` around lines 4183 -
4184, Replace the magic strings used when configuring ZNS ports by extracting
them into constants: replace the literal "br-int" passed to to.setBridgeName and
"openvswitch" passed to to.setBridgePortType with class-level constants (e.g.,
BRIDGE_NAME_INT and BRIDGE_PORT_TYPE_OPENVSWITCH) declared in KVMHost (or a
shared constants class) and use those constant names in the calls to
to.setBridgeName(...) and to.setBridgePortType(...); ensure the constants are
public/static/final and documented so all usages reference the single source of
truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java`:
- Around line 121-145: A VmNicVO is persisted by vnicFactory.createVmNic(nic,
spec) before callBeforeAllocateVmNicExtensions(...) but on extension failure the
code only records the error and doesn't remove that persisted nic, leaving an
orphaned VmNicVO/ResourceVO; in the Completion.fail(ErrorCode) branch delete the
persisted nicVO (and associated ResourceVO/any created system tags) inside a DB
transaction/SQLBatch (use dbf.remove(...) or equivalent) before adding the error
and calling wcomp.allDone(), ensuring the cleanup references the nicVO created
earlier so the rollback won't leave orphaned records.

In `@core/src/main/java/org/zstack/core/rest/webhook/WebhookCallbackClient.java`:
- Around line 101-109: The current submit method starts the timeout task via
thdf.submitTimeoutTask before putting the PendingEntry into pendingCalls, which
can race (timeout may run before put) and drop completion; fix by creating and
registering the PendingEntry in pendingCalls first (e.g., new
PendingEntry<>(completion, null) and put it), then call thdf.submitTimeoutTask
and store the returned TimeoutTaskReceipt into that PendingEntry (or replace the
entry with one containing the receipt). Update references: method submit, map
pendingCalls, class PendingEntry, thdf.submitTimeoutTask, fail/operr and
protocol.getCallbackPath() so the timeout handler will always find and remove
the registered entry.

In `@docs/modules/network/pages/networkResource/ZnsIntegration.adoc`:
- Around line 15-29: The example marked [source,go] is not valid Go: replace the
inline `###` comments with proper Go comments or remove them, add the missing
`struct` keyword for `type Segment` and ensure the slice field is declared
correctly (e.g., `CmsMetaDatas []Cms` with a proper struct tag like
`json:"cms"`), and make field names/types conform to Go syntax in the `Cms`
struct (e.g., ensure fields like `CmsUuid`, `Type`, `IP`, `Role`,
`CmsResourceUuid` use valid types and optional `json` tags); alternatively, if
you intend pseudocode, change the block language from `go` to something like
`text` and add a note that it’s pseudocode so readers aren’t misled.

In
`@header/src/main/java/org/zstack/header/vm/AfterReleaseVmNicExtensionPoint.java`:
- Around line 10-11: 为 AfterReleaseVmNicExtensionPoint 接口的 afterReleaseVmNic
方法添加方法级 Javadoc,说明回调契约:明确在何种时机框架会调用 afterReleaseVmNic,要求扩展实现必须在处理完成后且仅调用一次
Completion(调用 completion.success() 表示成功、调用 completion.fail(Throwable)
表示失败),不应阻塞调用线程或抛出未捕获异常,并说明是否允许异步完成及线程/事务约束;同时移除方法声明中多余的 public
修饰符以符合接口方法规范(定位符:AfterReleaseVmNicExtensionPoint.afterReleaseVmNic(VmNicInventory
nic, Completion completion))。

In `@network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java`:
- Line 197: 在 L3NetworkManagerImpl 中避免直接对 dbf.findByUuid(...) 的返回值调用
L3NetworkInventory.valueOf(...) 导致 NPE:先把 dbf.findByUuid(msg.getL3NetworkUuid(),
L3NetworkVO.class) 的结果赋给一个局部变量(例如 vo 或 l3Vo),对该变量判空;若为 null,调用
trigger.fail(...)(包含清晰的错误信息和 msg.getL3NetworkUuid())并返回;否则再调用
L3NetworkInventory.valueOf(...) 继续后续逻辑。

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2VlanNetworkBackend.java`:
- Around line 252-254: 当前在 KVMRealizeL2VlanNetworkBackend 的判断只为 dpdk
vhost-user(L2NetworkConstant.ACCEL_TYPE_VHOST_USER_SPACE)设置 srcPath,遗漏了
vDPA(L2NetworkConstant.ACCEL_TYPE_VDPA),导致 NIC 未被正确配置;在包含 to.setSrcPath(...)
的分支中扩展条件为同时匹配 ACCEL_TYPE_VHOST_USER_SPACE 或 ACCEL_TYPE_VDPA(检查 nic.getType()),使
to.setSrcPath 使用 L2NetworkConstant.OVN_DPDK_VNIC_SRC_PATH +
nic.getInternalName() 对两类加速 NIC 都生效;并在 KVMRealizeL2NoVlanNetworkBackend
中对称地做同样的修改以保持行为一致。

In
`@plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java`:
- Around line 273-279: The current try/catch in SdnControllerManagerImpl around
tagMgr.createTagsFromAPICreateMessage(...) and dbf.findByUuid(...) silently
swallows initialization failures and still calls publish(event); remove the
catch so failures propagate (or rethrow the exception) instead of continuing on
the success path, ensuring event.setInventory(...) and tag creation errors
prevent publish(event) from returning success; locate the block that calls
tagMgr.createTagsFromAPICreateMessage(...), dbf.findByUuid(...), and
event.setInventory(...) and delete the surrounding try/catch (or replace with a
rethrow) so errors are not suppressed.
- Around line 661-680: The loop building nicMaps in SdnControllerManagerImpl
(currently iterating nics and calling dbf.findByUuid for L3NetworkVO and
L2NetworkVO and reading L2_NETWORK_SDN_CONTROLLER_UUID tag per NIC) causes N+1
queries; refactor by adding a batch helper (e.g., buildNicToControllerMap or
resolveNicsToControllers) that: collects all l3 uuids from the nics, does a
single dbf.listByIds/findByUuids for L3NetworkVO, collects all referenced l2
uuids and batch-fetches L2NetworkVOs, then resolves controllerUuid tokens for
those L2s in one pass and returns a Map<controllerUuid, List<VmNicInventory>>;
replace the three similar loops (around the shown block and at ranges 698-717,
734-753) to call this helper and handle null/missing controllerUuid by failing
the completion once with the same operr message referencing l2 uuid.
- Around line 663-670: The loop that looks up L3NetworkVO and L2NetworkVO should
fail-fast instead of silently continuing: when
dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class) or
dbf.findByUuid(l3Vo.getL2NetworkUuid(), L2NetworkVO.class) returns null, stop
processing and fail the allocation (e.g., call completion.fail or throw an
exception) with a clear error message including the missing UUID and NIC info;
do not let nicMaps remain empty and return completion.success(). Update the
logic around shouldSkipSdnForNic to still allow skipping when intentional, but
treat null VO results as fatal data-inconsistency errors and surface them
immediately.

In `@rest/src/main/resources/scripts/RestDocumentationGenerator.groovy`:
- Around line 2846-2849: The loop currently only catches CloudRuntimeException
when calling checkMD(mdPath, it.value), so other runtime exceptions (e.g.,
IndexOutOfBoundsException, NullPointerException thrown from checkMD or
getExistGlobalConfigMarkDown) will break the loop and prevent collecting all
errors; update the try/catch to catch Exception (or RuntimeException) in
addition to CloudRuntimeException around the call sites (checkMD and any calls
to getExistGlobalConfigMarkDown) and add their messages to allErrors (preserving
existing CloudRuntimeException behavior) so the loop continues and aggregates
every markdown validation failure.
- Around line 851-861: 当前校验用的是 markDown.desc_CN.isEmpty() 等判断,但
GlobalConfigMarkDown 是从 new GlobalConfigMarkDown() 开始解析,默认值为 "PLACEHOLDER...",所以
.isEmpty() 会漏报未出现的 section。更新校验逻辑(在 RestDocumentationGenerator.groovy 中处理
getExistGlobalConfigMarkDown 返回的 GlobalConfigMarkDown)改为使用一个辅助判空方法(例如
isMissingField(String s))并在该方法中同时判断 null、trim().isEmpty() 和是否以
"PLACEHOLDER"(或项目中实际的占位符前缀)开头,替换对
desc_CN、name_CN、valueRangeRemark、defaultValueRemark、resourcesGranularitiesRemark、additionalRemark、backgroundInformation、isUIExposed、isCLIExposed
的所有 .isEmpty() 调用;或者相应地修改 getExistGlobalConfigMarkDown 使未在 markdown 中出现的字段返回
null,并据此做相同的判定。

---

Nitpick comments:
In `@header/src/main/java/org/zstack/header/network/l3/L3NetworkVO.java`:
- Around line 118-120: Extract the repeated type-checking logic into a single
static helper on L3NetworkType and call it from both L3NetworkVO and
L3NetworkInventory.enableIpAddressAllocation(): move the if
(L3NetworkType.hasType(getType())) { return
L3NetworkType.valueOf(getType()).isIpAddressAllocationEnabled(); } logic into a
new L3NetworkType.static method (e.g., isIpAddressAllocationEnabled(String type)
or isIpAddressAllocationEnabledFor(String type)) that performs the hasType check
and returns the flag, then replace the existing branches in L3NetworkVO and
L3NetworkInventory.enableIpAddressAllocation() to delegate to that new static
method to avoid duplication and future drift.

In
`@header/src/main/java/org/zstack/header/vm/AfterReleaseVmNicExtensionPoint.java`:
- Around line 6-11: The interface AfterReleaseVmNicExtensionPoint and its method
afterReleaseVmNic currently mix "Release" in the name with "deleted from
database" in the Javadoc; update either the name or the comment so lifecycle
semantics match: either rename the interface/method to
DeletedVmNicExtensionPoint/afterDeletedVmNic if it is invoked after DB deletion,
or change the Javadoc to state the exact "release" stage (e.g., resource
release/cleanup before/after DB deletion) if the name is correct; make the
change where AfterReleaseVmNicExtensionPoint and afterReleaseVmNic are declared
and ensure VmNicInventory references and any callers/registrations are updated
consistently to avoid mismatched lifecycle semantics.

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java`:
- Around line 4183-4184: Replace the magic strings used when configuring ZNS
ports by extracting them into constants: replace the literal "br-int" passed to
to.setBridgeName and "openvswitch" passed to to.setBridgePortType with
class-level constants (e.g., BRIDGE_NAME_INT and BRIDGE_PORT_TYPE_OPENVSWITCH)
declared in KVMHost (or a shared constants class) and use those constant names
in the calls to to.setBridgeName(...) and to.setBridgePortType(...); ensure the
constants are public/static/final and documented so all usages reference the
single source of truth.

In `@testlib/src/main/java/org/zstack/testlib/SdnControllerSpec.groovy`:
- Around line 62-64: 在使用 JSONObjectUtil.rehashObject(querySdnController {
conditions=["uuid=${inventory.uuid}".toString()] }[0],
SdnControllerInventory.class) 之前不要直接取索引 0;在 SdnControllerSpec 的 postCreate
流程里先检查 querySdnController 返回的结果是否为 null 或为空列表(例如检查变量或返回值的
size/empty),若为空则抛出或记录一个明确的错误(包含 inventory.uuid 和上下文信息),否则再取第一项并传入
JSONObjectUtil.rehashObject 转换为 SdnControllerInventory。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: 190b27bf-f5cc-42a9-9b21-26c5d0efaf60

📥 Commits

Reviewing files that changed from the base of the PR and between 73ca9d8 and 2254aba.

⛔ Files ignored due to path filters (9)
  • build/pom.xml is excluded by !**/*.xml
  • conf/springConfigXml/VmInstanceManager.xml is excluded by !**/*.xml
  • conf/springConfigXml/sdnController.xml is excluded by !**/*.xml
  • sdk/src/main/java/SourceClassMap.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateL2GeneveNetworkAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/L2GeneveNetworkInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/NicTO.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ZnsControllerInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ZnsTransportZoneInventory.java is excluded by !sdk/**
📒 Files selected for processing (40)
  • compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmAllocateSdnNicFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
  • compute/src/main/java/org/zstack/compute/vm/VmNicManagerImpl.java
  • compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java
  • conf/db/upgrade/V5.5.18__schema.sql
  • core/src/main/java/org/zstack/core/rest/webhook/WebhookCallbackClient.java
  • core/src/main/java/org/zstack/core/rest/webhook/WebhookProtocol.java
  • docs/modules/network/nav.adoc
  • docs/modules/network/pages/networkResource/ZStackL2NetworkType.adoc
  • docs/modules/network/pages/networkResource/ZnsIntegration.adoc
  • docs/modules/network/pages/networkResource/networkResource.adoc
  • header/src/main/java/org/zstack/header/network/l2/L2NetworkConstant.java
  • header/src/main/java/org/zstack/header/network/l3/AfterSetL3NetworkMtuExtensionPoint.java
  • header/src/main/java/org/zstack/header/network/l3/L3NetworkInventory.java
  • header/src/main/java/org/zstack/header/network/l3/L3NetworkType.java
  • header/src/main/java/org/zstack/header/network/l3/L3NetworkVO.java
  • header/src/main/java/org/zstack/header/vm/AfterAllocateSdnNicExtensionPoint.java
  • header/src/main/java/org/zstack/header/vm/AfterAllocateVmNicIpExtensionPoint.java
  • header/src/main/java/org/zstack/header/vm/AfterReleaseVmNicExtensionPoint.java
  • header/src/main/java/org/zstack/header/vm/BeforeAllocateVmNicExtensionPoint.java
  • header/src/main/java/org/zstack/header/vm/VmInstanceNicFactory.java
  • header/src/main/java/org/zstack/header/vm/VmOvsNicConstant.java
  • network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2NoVlanNetworkBackend.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2VlanNetworkBackend.java
  • plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java
  • plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerFactory.java
  • plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java
  • plugin/sdnController/src/main/java/org/zstack/sdnController/h3cVcfc/H3cVcfcSdnControllerFactory.java
  • plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/controller/SugonSdnControllerFactory.java
  • rest/src/main/resources/scripts/RestDocumentationGenerator.groovy
  • testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
  • testlib/src/main/java/org/zstack/testlib/SdnControllerSpec.groovy
  • utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java
💤 Files with no reviewable changes (4)
  • plugin/sdnController/src/main/java/org/zstack/sdnController/h3cVcfc/H3cVcfcSdnControllerFactory.java
  • plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerFactory.java
  • header/src/main/java/org/zstack/header/vm/VmOvsNicConstant.java
  • plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/controller/SugonSdnControllerFactory.java

Comment on lines +121 to +145
// Persist VmNicVO first so that ResourceVO entry exists before extensions
// (e.g. SDN controllers) attempt to create SystemTags referencing the NIC UUID.
VmNicVO nicVO = vnicFactory.createVmNic(nic, spec);

nic.setDeviceId(deviceId);
nic.setInternalName(VmNicVO.generateNicInternalName(spec.getVmInventory().getInternalId(), nic.getDeviceId()));
nic.setState(disableL3Networks.contains(nic.getL3NetworkUuid()) ? VmNicState.disable.toString() : VmNicState.enable.toString());
new SQLBatch() {
callBeforeAllocateVmNicExtensions(nic, spec, new Completion(wcomp) {
@Override
protected void scripts() {
VmNicVO nicVO = vnicFactory.createVmNic(nic, spec);
if (!nw.enableIpAddressAllocation() && nicNetworkInfoMap != null
&& nicNetworkInfoMap.containsKey(nw.getUuid())
&& spec.getVmInventory().getType().equals(VmInstanceConstant.USER_VM_TYPE)) {
NicIpAddressInfo nicNicIpAddressInfo = nicNetworkInfoMap.get(nic.getL3NetworkUuid());
if (!nicNicIpAddressInfo.ipv6Address.isEmpty()) {
UsedIpVO vo = new UsedIpVO();
vo.setUuid(Platform.getUuid());
vo.setIp(IPv6NetworkUtils.getIpv6AddressCanonicalString(nicNicIpAddressInfo.ipv6Address));
vo.setNetmask(IPv6NetworkUtils.getFormalNetmaskOfNetworkCidr(nicNicIpAddressInfo.ipv6Address+"/"+ nicNicIpAddressInfo.ipv6Prefix));
vo.setGateway(nicNicIpAddressInfo.ipv6Gateway.isEmpty() ? "" : IPv6NetworkUtils.getIpv6AddressCanonicalString(nicNicIpAddressInfo.ipv6Gateway));
vo.setIpVersion(IPv6Constants.IPv6);
vo.setVmNicUuid(nic.getUuid());
vo.setL3NetworkUuid(nic.getL3NetworkUuid());
vo.setIpInBinary(NetworkUtils.ipStringToBytes(vo.getIp()));
vo.setIpRangeUuid(new StaticIpOperator().getIpRangeUuid(nic.getL3NetworkUuid(), vo.getIp()));
nic.setUsedIpUuid(vo.getUuid());
nicVO.setUsedIpUuid(vo.getUuid());
nicVO.setIp(vo.getIp());
nicVO.setNetmask(vo.getNetmask());
nicVO.setGateway(vo.getGateway());
dbf.persist(vo);
public void success() {
new SQLBatch() {
@Override
protected void scripts() {
persistStaticIpIfNeeded(nic, nicVO, nw, nicNetworkInfoMap, spec);
nics.add(nic);
VmNicVO updated = dbf.updateAndRefresh(nicVO);
addVmNicConfig(updated, spec, nicSpec);
}
if (!nicNicIpAddressInfo.ipv4Address.isEmpty()) {
UsedIpVO vo = new UsedIpVO();
vo.setUuid(Platform.getUuid());
vo.setIp(nicNicIpAddressInfo.ipv4Address);
vo.setGateway(nicNicIpAddressInfo.ipv4Gateway);
vo.setNetmask(nicNicIpAddressInfo.ipv4Netmask);
vo.setIpVersion(IPv6Constants.IPv4);
vo.setVmNicUuid(nic.getUuid());
vo.setL3NetworkUuid(nic.getL3NetworkUuid());
vo.setIpInLong(NetworkUtils.ipv4StringToLong(vo.getIp()));
vo.setIpInBinary(NetworkUtils.ipStringToBytes(vo.getIp()));
vo.setIpRangeUuid(new StaticIpOperator().getIpRangeUuid(nic.getL3NetworkUuid(), vo.getIp()));
nic.setUsedIpUuid(vo.getUuid());
nicVO.setUsedIpUuid(vo.getUuid());
nicVO.setIp(vo.getIp());
nicVO.setNetmask(vo.getNetmask());
nicVO.setGateway(vo.getGateway());
dbf.persist(vo);
}
}
nics.add(nic);
nicVO = dbf.updateAndRefresh(nicVO);
addVmNicConfig(nicVO, spec, nicSpec);
}.execute();
wcomp.done();
}
}.execute();
wcomp.done();

@Override
public void fail(ErrorCode errorCode) {
errs.add(errorCode);
wcomp.allDone();
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

在扩展失败时清理已落库的 VmNicVO

Line 123 已经持久化了 VmNicVO,但只有 Line 132 成功分支才把这张 NIC 放进后续集合;Line 141-144 失败时只是记错并结束。当前回滚逻辑会遍历 spec.getDestNics(),它看不到这张尚未加入集合的 NIC,因此这里会遗留孤儿 VmNicVO/ResourceVO

🛠️ 一个直接的修复方向
             callBeforeAllocateVmNicExtensions(nic, spec, new Completion(wcomp) {
                 `@Override`
                 public void success() {
                     new SQLBatch() {
@@
                 `@Override`
                 public void fail(ErrorCode errorCode) {
+                    vnicFactory.releaseVmNic(nic);
                     errs.add(errorCode);
                     wcomp.allDone();
                 }
             });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java` around
lines 121 - 145, A VmNicVO is persisted by vnicFactory.createVmNic(nic, spec)
before callBeforeAllocateVmNicExtensions(...) but on extension failure the code
only records the error and doesn't remove that persisted nic, leaving an
orphaned VmNicVO/ResourceVO; in the Completion.fail(ErrorCode) branch delete the
persisted nicVO (and associated ResourceVO/any created system tags) inside a DB
transaction/SQLBatch (use dbf.remove(...) or equivalent) before adding the error
and calling wcomp.allDone(), ensuring the cleanup references the nicVO created
earlier so the rollback won't leave orphaned records.

Comment on lines +101 to +109
public String submit(ReturnValueCompletion<T> completion, TimeUnit unit, long timeout) {
String taskId = Platform.getUuid();

ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt = thdf.submitTimeoutTask(() -> {
fail(taskId, operr("[Webhook Timeout] callback timed out for taskId[%s], path[%s]",
taskId, protocol.getCallbackPath()));
}, unit, timeout);

pendingCalls.put(taskId, new PendingEntry<>(completion, timeoutReceipt));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

先注册 pending entry,再启动超时任务。

Line 104 在 Line 109 之前启动 timeout;如果 timeout == 0,或者调度线程立刻执行,fail(taskId, ...) 会先跑到 pendingCalls.remove(taskId) 并直接返回。随后 Line 109 再 put,这次调用就会变成一个永远不会超时、也不会完成的悬挂请求。

🛠️ 一个可行的修复模式
     private static class PendingEntry<T> {
         final ReturnValueCompletion<T> completion;
-        final ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt;
+        volatile ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt;
 
-        PendingEntry(ReturnValueCompletion<T> completion,
-                     ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt) {
+        PendingEntry(ReturnValueCompletion<T> completion) {
             this.completion = completion;
-            this.timeoutReceipt = timeoutReceipt;
         }
     }
@@
     public String submit(ReturnValueCompletion<T> completion, TimeUnit unit, long timeout) {
         String taskId = Platform.getUuid();
-
-        ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt = thdf.submitTimeoutTask(() -> {
-            fail(taskId, operr("[Webhook Timeout] callback timed out for taskId[%s], path[%s]",
-                    taskId, protocol.getCallbackPath()));
-        }, unit, timeout);
-
-        pendingCalls.put(taskId, new PendingEntry<>(completion, timeoutReceipt));
+        PendingEntry<T> entry = new PendingEntry<>(completion);
+        pendingCalls.put(taskId, entry);
+        entry.timeoutReceipt = thdf.submitTimeoutTask(() -> {
+            fail(taskId, operr("[Webhook Timeout] callback timed out for taskId[%s], path[%s]",
+                    taskId, protocol.getCallbackPath()));
+        }, unit, timeout);
         return taskId;
     }
@@
         PendingEntry<T> entry = pendingCalls.remove(taskId);
         if (entry != null) {
-            entry.timeoutReceipt.cancel();
+            if (entry.timeoutReceipt != null) {
+                entry.timeoutReceipt.cancel();
+            }
             entry.completion.fail(error);
         }
@@
         PendingEntry<T> entry = pendingCalls.remove(taskId);
         if (entry == null) {
@@
-        entry.timeoutReceipt.cancel();
+        if (entry.timeoutReceipt != null) {
+            entry.timeoutReceipt.cancel();
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/main/java/org/zstack/core/rest/webhook/WebhookCallbackClient.java`
around lines 101 - 109, The current submit method starts the timeout task via
thdf.submitTimeoutTask before putting the PendingEntry into pendingCalls, which
can race (timeout may run before put) and drop completion; fix by creating and
registering the PendingEntry in pendingCalls first (e.g., new
PendingEntry<>(completion, null) and put it), then call thdf.submitTimeoutTask
and store the returned TimeoutTaskReceipt into that PendingEntry (or replace the
entry with one containing the receipt). Update references: method submit, map
pendingCalls, class PendingEntry, thdf.submitTimeoutTask, fail/operr and
protocol.getCallbackPath() so the timeout handler will always find and remove
the registered entry.

Comment on lines +15 to +29
[source,go]
----
type Cms struct {
CmsUuid string
Type string ### cloud/zsv/zaku/zns
IP string ### cloud mn vip
Role string ###owner, user
CmsResourceUuid string ###owner, user
}

type Segment {
...
CmsMetaDatas []Cms `json:"cms"`
}
----
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

把这段示例改成合法 Go,或者明确标成伪代码。

这里标了 [source,go],但片段本身不是有效 Go:### 不是 Go 注释,type Segment { 也缺少 struct。这会误导读者直接复制示例,也会让语法高亮给出错误暗示。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/modules/network/pages/networkResource/ZnsIntegration.adoc` around lines
15 - 29, The example marked [source,go] is not valid Go: replace the inline
`###` comments with proper Go comments or remove them, add the missing `struct`
keyword for `type Segment` and ensure the slice field is declared correctly
(e.g., `CmsMetaDatas []Cms` with a proper struct tag like `json:"cms"`), and
make field names/types conform to Go syntax in the `Cms` struct (e.g., ensure
fields like `CmsUuid`, `Type`, `IP`, `Role`, `CmsResourceUuid` use valid types
and optional `json` tags); alternatively, if you intend pseudocode, change the
block language from `go` to something like `text` and add a note that it’s
pseudocode so readers aren’t misled.

Comment on lines +10 to +11
public interface AfterReleaseVmNicExtensionPoint {
void afterReleaseVmNic(VmNicInventory nic, Completion completion);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

请为接口方法补充方法级 Javadoc,明确回调契约。

当前只有接口级说明,Completion 的调用约束(例如成功/失败分支和调用时机)不够明确,扩展实现方容易误用。

建议补丁
 public interface AfterReleaseVmNicExtensionPoint {
+    /**
+     * Called after VmNic reaches this extension point stage.
+     * Implementations must complete via {`@code` completion.success()} or {`@code` completion.fail()}.
+     */
     void afterReleaseVmNic(VmNicInventory nic, Completion completion);
 }

As per coding guidelines, “接口方法不应有多余的修饰符(例如 public),且必须配有有效的 Javadoc 注释”。

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public interface AfterReleaseVmNicExtensionPoint {
void afterReleaseVmNic(VmNicInventory nic, Completion completion);
public interface AfterReleaseVmNicExtensionPoint {
/**
* Called after VmNic reaches this extension point stage.
* Implementations must complete via {`@code` completion.success()} or {`@code` completion.fail()}.
*/
void afterReleaseVmNic(VmNicInventory nic, Completion completion);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@header/src/main/java/org/zstack/header/vm/AfterReleaseVmNicExtensionPoint.java`
around lines 10 - 11, 为 AfterReleaseVmNicExtensionPoint 接口的 afterReleaseVmNic
方法添加方法级 Javadoc,说明回调契约:明确在何种时机框架会调用 afterReleaseVmNic,要求扩展实现必须在处理完成后且仅调用一次
Completion(调用 completion.success() 表示成功、调用 completion.fail(Throwable)
表示失败),不应阻塞调用线程或抛出未捕获异常,并说明是否允许异步完成及线程/事务约束;同时移除方法声明中多余的 public
修饰符以符合接口方法规范(定位符:AfterReleaseVmNicExtensionPoint.afterReleaseVmNic(VmNicInventory
nic, Completion completion))。

return;
}

L3NetworkInventory l3Inv = L3NetworkInventory.valueOf(dbf.findByUuid(msg.getL3NetworkUuid(), L3NetworkVO.class));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

请为 L3 查询结果增加空值保护,避免 NPE。

Line 197 直接对 dbf.findByUuid(...) 结果做 L3NetworkInventory.valueOf(...),在并发删除等场景下可能触发空指针并返回不明确错误。建议先判空并显式 trigger.fail(...)

💡建议修改
-                L3NetworkInventory l3Inv = L3NetworkInventory.valueOf(dbf.findByUuid(msg.getL3NetworkUuid(), L3NetworkVO.class));
+                L3NetworkVO l3Vo = dbf.findByUuid(msg.getL3NetworkUuid(), L3NetworkVO.class);
+                if (l3Vo == null) {
+                    trigger.fail(operr("cannot find l3Network[uuid:%s]", msg.getL3NetworkUuid()));
+                    return;
+                }
+                L3NetworkInventory l3Inv = L3NetworkInventory.valueOf(l3Vo);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java` at
line 197, 在 L3NetworkManagerImpl 中避免直接对 dbf.findByUuid(...) 的返回值调用
L3NetworkInventory.valueOf(...) 导致 NPE:先把 dbf.findByUuid(msg.getL3NetworkUuid(),
L3NetworkVO.class) 的结果赋给一个局部变量(例如 vo 或 l3Vo),对该变量判空;若为 null,调用
trigger.fail(...)(包含清晰的错误信息和 msg.getL3NetworkUuid())并返回;否则再调用
L3NetworkInventory.valueOf(...) 继续后续逻辑。

Comment on lines +273 to +279
try {
tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), SdnControllerVO.class.getSimpleName());
event.setInventory(SdnControllerInventory.valueOf(dbf.findByUuid(vo.getUuid(), SdnControllerVO.class)));
} catch (Exception e) {
logger.warn(String.format("failed to load SdnControllerVO[uuid:%s] after init: %s",
vo.getUuid(), e.getMessage()), e);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

不要在成功路径里吞掉初始化失败。

Line 274 或 Line 275 任一失败,这里都会继续 publish(event),调用方仍会收到 success,但 tag 可能没建、inventory 也可能为空。这个分支会把“创建成功”变成静默的部分成功。

Based on learnings 在 ZStack 中,资源创建成功路径的 tagMgr.createTagsFromAPICreateMessage(...) 和后续 dbf.findByUuid(...) 不需要用 try/catch 来防御“资源不存在”异常,应依赖生命周期和数据库约束保证一致性。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java`
around lines 273 - 279, The current try/catch in SdnControllerManagerImpl around
tagMgr.createTagsFromAPICreateMessage(...) and dbf.findByUuid(...) silently
swallows initialization failures and still calls publish(event); remove the
catch so failures propagate (or rethrow the exception) instead of continuing on
the success path, ensuring event.setInventory(...) and tag creation errors
prevent publish(event) from returning success; locate the block that calls
tagMgr.createTagsFromAPICreateMessage(...), dbf.findByUuid(...), and
event.setInventory(...) and delete the surrounding try/catch (or replace with a
rethrow) so errors are not suppressed.

Comment on lines +661 to +680
Map<String, List<VmNicInventory>> nicMaps = new HashMap<>();
for (VmNicInventory nic : nics) {
L3NetworkVO l3Vo = dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class);
if (l3Vo == null) {
continue;
}

L2NetworkVO l2VO = dbf.findByUuid(l3Vo.getL2NetworkUuid(), L2NetworkVO.class);
if (l2VO == null || shouldSkipSdnForNic(l2VO)) {
continue;
}

String controllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID.getTokenByResourceUuid(
l2VO.getUuid(), L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN);
if (controllerUuid == null) {
completion.fail(operr(ORG_ZSTACK_SDNCONTROLLER_10006, "sdn l2 network[uuid:%s] has not attached controller", l2VO.getUuid()));
return;
}

nicMaps.computeIfAbsent(controllerUuid, k -> new ArrayList<>()).add(nic);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

把 NIC→controller 解析提成一个批量 helper。

这三段逻辑对每个 NIC 都做两次 findByUuid(),而且几乎完全复制了三遍;NIC 数量一多就会退化成明显的 N+1 查询。建议先批量取 L3NetworkVO / L2NetworkVO / controller tag 映射,再复用一个公共 helper 生成 nicMaps

As per coding guidelines “禁止循环里套查询,避免嵌套查询带来的性能问题”。

Also applies to: 698-717, 734-753

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java`
around lines 661 - 680, The loop building nicMaps in SdnControllerManagerImpl
(currently iterating nics and calling dbf.findByUuid for L3NetworkVO and
L2NetworkVO and reading L2_NETWORK_SDN_CONTROLLER_UUID tag per NIC) causes N+1
queries; refactor by adding a batch helper (e.g., buildNicToControllerMap or
resolveNicsToControllers) that: collects all l3 uuids from the nics, does a
single dbf.listByIds/findByUuids for L3NetworkVO, collects all referenced l2
uuids and batch-fetches L2NetworkVOs, then resolves controllerUuid tokens for
those L2s in one pass and returns a Map<controllerUuid, List<VmNicInventory>>;
replace the three similar loops (around the shown block and at ranges 698-717,
734-753) to call this helper and handle null/missing controllerUuid by failing
the completion once with the same operr message referencing l2 uuid.

Comment on lines +663 to +670
L3NetworkVO l3Vo = dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class);
if (l3Vo == null) {
continue;
}

L2NetworkVO l2VO = dbf.findByUuid(l3Vo.getL2NetworkUuid(), L2NetworkVO.class);
if (l2VO == null || shouldSkipSdnForNic(l2VO)) {
continue;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

分配路径不应静默跳过缺失的网络对象。

这里对缺失的 L3NetworkVO / L2NetworkVO 直接 continue,最终 nicMaps 可能为空并返回 completion.success()。结果就是 VM 流程继续成功,但 SDN 端口根本没有创建。分配路径应该 fail-fast,而不是把这种数据不一致当成可忽略情况。

🛠️ 一个更安全的处理方式
         for (VmNicInventory nic : nics) {
             L3NetworkVO l3Vo = dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class);
             if (l3Vo == null) {
-                continue;
+                completion.fail(operr("cannot find l3Network[uuid:%s] for vmNic[uuid:%s]",
+                        nic.getL3NetworkUuid(), nic.getUuid()));
+                return;
             }
 
             L2NetworkVO l2VO = dbf.findByUuid(l3Vo.getL2NetworkUuid(), L2NetworkVO.class);
-            if (l2VO == null || shouldSkipSdnForNic(l2VO)) {
-                continue;
+            if (l2VO == null) {
+                completion.fail(operr("cannot find l2Network[uuid:%s] for vmNic[uuid:%s]",
+                        l3Vo.getL2NetworkUuid(), nic.getUuid()));
+                return;
+            }
+            if (shouldSkipSdnForNic(l2VO)) {
+                continue;
             }

Based on learnings backend realization code can safely assume the existence of the VO for the resource being operated on due to lifecycle management and strong foreign key constraints in the database schema.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java`
around lines 663 - 670, The loop that looks up L3NetworkVO and L2NetworkVO
should fail-fast instead of silently continuing: when
dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class) or
dbf.findByUuid(l3Vo.getL2NetworkUuid(), L2NetworkVO.class) returns null, stop
processing and fail the allocation (e.g., call completion.fail or throw an
exception) with a clear error message including the missing UUID and NIC info;
do not let nicMaps remain empty and return completion.success(). Update the
logic around shouldSkipSdnForNic to still allow skipping when intentional, but
treat null VO results as fatal data-inconsistency errors and surface them
immediately.

Comment on lines +851 to +861
GlobalConfigMarkDown markDown = getExistGlobalConfigMarkDown(mdPath)
List<String> missingFields = []
if (markDown.desc_CN.isEmpty()) missingFields.add("desc_CN")
if (markDown.name_CN.isEmpty()) missingFields.add("name_CN")
if (markDown.valueRangeRemark.isEmpty()) missingFields.add("valueRangeRemark")
if (markDown.defaultValueRemark.isEmpty()) missingFields.add("defaultValueRemark")
if (markDown.resourcesGranularitiesRemark.isEmpty()) missingFields.add("resourcesGranularitiesRemark")
if (markDown.additionalRemark.isEmpty()) missingFields.add("additionalRemark")
if (markDown.backgroundInformation.isEmpty()) missingFields.add("backgroundInformation")
if (markDown.isUIExposed.isEmpty()) missingFields.add("isUIExposed")
if (markDown.isCLIExposed.isEmpty()) missingFields.add("isCLIExposed")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

缺失章节会被误判为已填写

这里用 .isEmpty() 判断必填项会漏报整段缺失的 section。getExistGlobalConfigMarkDown() 是从 new GlobalConfigMarkDown() 开始解析的,这些字段默认都是非空的 PLACEHOLDER...;如果 markdown 里根本没有对应标题,解析后字段仍然不是空串,所以当前校验会直接通过。

建议修复
         GlobalConfigMarkDown markDown = getExistGlobalConfigMarkDown(mdPath)
         List<String> missingFields = []
-        if (markDown.desc_CN.isEmpty()) missingFields.add("desc_CN")
-        if (markDown.name_CN.isEmpty()) missingFields.add("name_CN")
-        if (markDown.valueRangeRemark.isEmpty()) missingFields.add("valueRangeRemark")
-        if (markDown.defaultValueRemark.isEmpty()) missingFields.add("defaultValueRemark")
-        if (markDown.resourcesGranularitiesRemark.isEmpty()) missingFields.add("resourcesGranularitiesRemark")
-        if (markDown.additionalRemark.isEmpty()) missingFields.add("additionalRemark")
-        if (markDown.backgroundInformation.isEmpty()) missingFields.add("backgroundInformation")
-        if (markDown.isUIExposed.isEmpty()) missingFields.add("isUIExposed")
-        if (markDown.isCLIExposed.isEmpty()) missingFields.add("isCLIExposed")
+        def missing = { String value ->
+            StringUtils.isBlank(value) || value.startsWith(PLACEHOLDER)
+        }
+        if (missing(markDown.desc_CN)) missingFields.add("desc_CN")
+        if (missing(markDown.name_CN)) missingFields.add("name_CN")
+        if (missing(markDown.valueRangeRemark)) missingFields.add("valueRangeRemark")
+        if (missing(markDown.defaultValueRemark)) missingFields.add("defaultValueRemark")
+        if (missing(markDown.resourcesGranularitiesRemark)) missingFields.add("resourcesGranularitiesRemark")
+        if (missing(markDown.additionalRemark)) missingFields.add("additionalRemark")
+        if (missing(markDown.backgroundInformation)) missingFields.add("backgroundInformation")
+        if (missing(markDown.isUIExposed)) missingFields.add("isUIExposed")
+        if (missing(markDown.isCLIExposed)) missingFields.add("isCLIExposed")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rest/src/main/resources/scripts/RestDocumentationGenerator.groovy` around
lines 851 - 861, 当前校验用的是 markDown.desc_CN.isEmpty() 等判断,但 GlobalConfigMarkDown
是从 new GlobalConfigMarkDown() 开始解析,默认值为 "PLACEHOLDER...",所以 .isEmpty() 会漏报未出现的
section。更新校验逻辑(在 RestDocumentationGenerator.groovy 中处理
getExistGlobalConfigMarkDown 返回的 GlobalConfigMarkDown)改为使用一个辅助判空方法(例如
isMissingField(String s))并在该方法中同时判断 null、trim().isEmpty() 和是否以
"PLACEHOLDER"(或项目中实际的占位符前缀)开头,替换对
desc_CN、name_CN、valueRangeRemark、defaultValueRemark、resourcesGranularitiesRemark、additionalRemark、backgroundInformation、isUIExposed、isCLIExposed
的所有 .isEmpty() 调用;或者相应地修改 getExistGlobalConfigMarkDown 使未在 markdown 中出现的字段返回
null,并据此做相同的判定。

Comment on lines +2846 to +2849
try {
checkMD(mdPath, it.value)
} catch (CloudRuntimeException e) {
allErrors.add(e.message)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

这里只聚合了 CloudRuntimeException,仍然会在首个解析异常处提前退出

新逻辑的目标是汇总所有 markdown 校验失败,但 checkMD() / getExistGlobalConfigMarkDown() 里仍可能抛出普通 RuntimeException(例如 markdown 结构异常触发的 IndexOutOfBoundsExceptionNullPointerException)。当前只 catch CloudRuntimeException,这类文件一旦出现,循环还是会直接中断,最终拿不到完整的错误列表。

建议修复
             try {
                 checkMD(mdPath, it.value)
-            } catch (CloudRuntimeException e) {
-                allErrors.add(e.message)
+            } catch (Exception e) {
+                allErrors.add(e.message ?: "Validation failed for ${mdPath}: ${e.class.name}")
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rest/src/main/resources/scripts/RestDocumentationGenerator.groovy` around
lines 2846 - 2849, The loop currently only catches CloudRuntimeException when
calling checkMD(mdPath, it.value), so other runtime exceptions (e.g.,
IndexOutOfBoundsException, NullPointerException thrown from checkMD or
getExistGlobalConfigMarkDown) will break the loop and prevent collecting all
errors; update the try/catch to catch Exception (or RuntimeException) in
addition to CloudRuntimeException around the call sites (checkMD and any calls
to getExistGlobalConfigMarkDown) and add their messages to allErrors (preserving
existing CloudRuntimeException behavior) so the loop continues and aggregates
every markdown validation failure.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java (1)

722-739: 这里建议别再新增一段 TypedQuery 查询分支。

这段和上面的 L3 分支结构几乎一致,但继续引入 JPQL 字符串会让字段变更更脆弱,也把相同的筛选条件又复制了一遍。更合适的是抽一个按网络范围查 VM 的 helper,并改用现有的 Q.New(...) / SQL.New(...) 风格收敛实现。

As per coding guidelines 新增代码里面避免使用 TypedQuery

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java` around
lines 722 - 739, The new anonymous Callable in VmCascadeExtension introduces a
raw TypedQuery JPQL branch to fetch VMs by L2 network UUIDs; extract this into a
shared helper (e.g., queryVmsByNetworkRange or findVmsByL2NetworkUuids) and
reimplement it using the project-standard Q.New(...) / SQL.New(...) API used by
the existing L3 branch, reusing the same filters (vm.type ==
VmInstanceConstant.USER_VM_TYPE, vm.state in {Stopped, Paused, Running,
Destroyed}, join through VmNicVO -> L3NetworkVO -> L2Network UUIDs) instead of
adding another TypedQuery; update the Callable to call that helper and remove
the JPQL string so the code follows the coding guideline of avoiding TypedQuery.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java`:
- Around line 339-343: The current L2 delete flow first derives l3Uuids via
Q.New(L3NetworkVO).in(L3NetworkVO_.l2NetworkUuid, l2Uuids) and later re-queries
NIC/VMs using nic.l3NetworkUuid, but VmNicVO.l3NetworkUuid is SET_NULL on L3
delete so NICs can be missed if L3s were processed earlier; instead, compute the
affected VmNic/VM set once up-front for the L2 cascade (e.g. query VmNicVO by
the L2 identifier(s) or by joined criteria that do not rely on L3NetworkVO
existence), store that NIC/VM list (used where msgs is built) and reuse it in
subsequent steps instead of re-querying via L3NetworkVO/l3.uuid so the detach
logic always runs for those NICs/VMs.

---

Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java`:
- Around line 722-739: The new anonymous Callable in VmCascadeExtension
introduces a raw TypedQuery JPQL branch to fetch VMs by L2 network UUIDs;
extract this into a shared helper (e.g., queryVmsByNetworkRange or
findVmsByL2NetworkUuids) and reimplement it using the project-standard
Q.New(...) / SQL.New(...) API used by the existing L3 branch, reusing the same
filters (vm.type == VmInstanceConstant.USER_VM_TYPE, vm.state in {Stopped,
Paused, Running, Destroyed}, join through VmNicVO -> L3NetworkVO -> L2Network
UUIDs) instead of adding another TypedQuery; update the Callable to call that
helper and remove the JPQL string so the code follows the coding guideline of
avoiding TypedQuery.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: 4f4af80d-cd65-4c77-b7ad-ec1b4e60ef76

📥 Commits

Reviewing files that changed from the base of the PR and between 2254aba and 7998ea6.

📒 Files selected for processing (1)
  • compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java

Comment on lines +339 to +343
List<String> l3Uuids = Q.New(L3NetworkVO.class).in(L3NetworkVO_.l2NetworkUuid, l2Uuids)
.select(L3NetworkVO_.uuid).listValues();
if (l3Uuids.isEmpty()) {
return msgs;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

L2 删除路径不要依赖仍然存在的 L3NetworkVO 记录。

这里一处先通过 L2 -> L3 现查 UUID,另一处再通过 nic.l3NetworkUuid = l3.uuid 反查 VM。结合 header/src/main/java/org/zstack/header/vm/VmNicVO.java:35-39,L3 删除时 VmNicVO.l3NetworkUuid 会被 SET_NULL;如果同一条 cascade 里 L3 已先处理,这两个查询都会漏掉目标 NIC/VM,最终这次新增的 detach 逻辑就不会触发。建议把 L2 删除命中的 NIC/VM 一次性算出来并沿用,避免在后续步骤再次依赖 L3 行仍在库里。

Also applies to: 726-731

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java` around
lines 339 - 343, The current L2 delete flow first derives l3Uuids via
Q.New(L3NetworkVO).in(L3NetworkVO_.l2NetworkUuid, l2Uuids) and later re-queries
NIC/VMs using nic.l3NetworkUuid, but VmNicVO.l3NetworkUuid is SET_NULL on L3
delete so NICs can be missed if L3s were processed earlier; instead, compute the
affected VmNic/VM set once up-front for the L2 cascade (e.g. query VmNicVO by
the L2 identifier(s) or by joined criteria that do not rely on L3NetworkVO
existence), store that NIC/VM list (used where msgs is built) and reuse it in
subsequent steps instead of re-querying via L3NetworkVO/l3.uuid so the detach
logic always runs for those NICs/VMs.

ruansteve and others added 3 commits April 10, 2026 13:16
Resolves: ZCF-2063

Change-Id: Ib7ed3b9a111bad1ec0d6f76ece259bfd25444bb7
<fix>[zns]: add ZNS error code ORG_ZSTACK_NETWORK_ZNS_10011

See merge request zstackio/zstack!9584
AttachL2NetworkToCluster incorrectly blocks ZNS L2
networks. Three validation points assume all L2s use
host physical interfaces. ZNS is an SDN controller,
physicalInterface is always "" and meaningless.

Fix 1: L2NetworkApiInterceptor - skip vSwitchType
       conflict check for ZNS L2
Fix 2: KVMApiInterceptor - skip vlan device name
       length check for ZNS L2
Fix 3: L2NoVlanNetwork - skip physicalInterface
       conflict for ZNS L2NoVlan, exclude ZNS
       L2Vlan from non-SDN overlap query

Resolves: ZCF-2073

Change-Id: I757966777a64736a6d646178786e6b70617a7a77

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MatheMatrix MatheMatrix force-pushed the sync/shixin.ruan/shixin.ruan-zcf-2047@@2 branch from 7998ea6 to 4baf00d Compare April 13, 2026 05:22
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java (2)

44-45: 未使用的字段 vmMgr

VmInstanceManager vmMgr 已注入但在本文件中从未使用。请移除该字段以保持代码整洁。

♻️ 建议移除未使用的字段
-@Autowired
-private VmInstanceManager vmMgr;
 `@Autowired`
 private PluginRegistry pluginRgty;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java` around
lines 44 - 45, Remove the unused injected field VmInstanceManager vmMgr from the
VmDetachNicFlow class; locate the private field declaration "private
VmInstanceManager vmMgr" annotated with `@Autowired` in VmDetachNicFlow and delete
that field and its `@Autowired` annotation to clean up unused code and imports.

120-125: VmReturnReleaseNicFlow 中的实现存在细微差异

此处仅检查 exts.isEmpty(),而 VmReturnReleaseNicFlow.callReleaseSdnNics 还额外检查了 nics.isEmpty()。虽然当前调用方 (line 101) 总是传入 singletonList,不会为空,但为保持一致性和防御性编程,建议统一检查条件。

♻️ 建议与 VmReturnReleaseNicFlow 保持一致
 private void callReleaseSdnNics(List<VmNicInventory> nics, Completion completion) {
     List<AfterAllocateSdnNicExtensionPoint> exts = pluginRgty.getExtensionList(AfterAllocateSdnNicExtensionPoint.class);
-    if (exts.isEmpty()) {
+    if (exts.isEmpty() || nics.isEmpty()) {
         completion.success();
         return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java` around
lines 120 - 125, The callReleaseSdnNics method in VmDetachNicFlow only checks
whether extension list is empty but omits the defensive check for an empty nics
list (VmReturnReleaseNicFlow.callReleaseSdnNics also checks nics.isEmpty());
update VmDetachNicFlow.callReleaseSdnNics to return completion.success() when
either pluginRgty.getExtensionList(AfterAllocateSdnNicExtensionPoint.class) is
empty OR the passed-in nics list is empty, mirroring the logic in
VmReturnReleaseNicFlow and preventing unnecessary extension invocation when nics
is empty.
compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java (2)

40-41: 未使用的字段 vmMgr

VmInstanceManager vmMgr 已注入但在本文件中从未使用。请移除该字段以保持代码整洁。

♻️ 建议移除未使用的字段
 `@Autowired`
 protected PluginRegistry pluginRgty;
-@Autowired
-protected VmInstanceManager vmMgr;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java`
around lines 40 - 41, Remove the unused injected field by deleting the protected
VmInstanceManager vmMgr declaration (including its `@Autowired` annotation) from
the VmReturnReleaseNicFlow class; also remove any now-unused import for
VmInstanceManager and ensure there are no remaining references to vmMgr
elsewhere in this class.

120-146: callReleaseSdnNics 方法与 VmDetachNicFlow 中高度重复

该方法与 VmDetachNicFlow.java 中的 callReleaseSdnNics 实现几乎完全相同。建议将此方法提取到公共工具类或基类中,以遵循 DRY 原则并减少维护成本。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java`
around lines 120 - 146, The callReleaseSdnNics implementation is duplicated
between VmReturnReleaseNicFlow and VmDetachNicFlow; extract the shared logic
into a single reusable utility or base method (e.g., a static helper in a new
SdnNicReleaseUtils or a protected method on a common base class) that accepts
List<VmNicInventory> nics, PluginRegistry/PluginRgty access, and a Completion,
and have both VmReturnReleaseNicFlow.callReleaseSdnNics and
VmDetachNicFlow.delegate to that new method; ensure the helper uses the same
AfterAllocateSdnNicExtensionPoint iteration,
Completion/While/WhileDoneCompletion behavior, and logging semantics so callers
simply forward nics and completion to the consolidated routine.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java`:
- Around line 44-45: Remove the unused injected field VmInstanceManager vmMgr
from the VmDetachNicFlow class; locate the private field declaration "private
VmInstanceManager vmMgr" annotated with `@Autowired` in VmDetachNicFlow and delete
that field and its `@Autowired` annotation to clean up unused code and imports.
- Around line 120-125: The callReleaseSdnNics method in VmDetachNicFlow only
checks whether extension list is empty but omits the defensive check for an
empty nics list (VmReturnReleaseNicFlow.callReleaseSdnNics also checks
nics.isEmpty()); update VmDetachNicFlow.callReleaseSdnNics to return
completion.success() when either
pluginRgty.getExtensionList(AfterAllocateSdnNicExtensionPoint.class) is empty OR
the passed-in nics list is empty, mirroring the logic in VmReturnReleaseNicFlow
and preventing unnecessary extension invocation when nics is empty.

In `@compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java`:
- Around line 40-41: Remove the unused injected field by deleting the protected
VmInstanceManager vmMgr declaration (including its `@Autowired` annotation) from
the VmReturnReleaseNicFlow class; also remove any now-unused import for
VmInstanceManager and ensure there are no remaining references to vmMgr
elsewhere in this class.
- Around line 120-146: The callReleaseSdnNics implementation is duplicated
between VmReturnReleaseNicFlow and VmDetachNicFlow; extract the shared logic
into a single reusable utility or base method (e.g., a static helper in a new
SdnNicReleaseUtils or a protected method on a common base class) that accepts
List<VmNicInventory> nics, PluginRegistry/PluginRgty access, and a Completion,
and have both VmReturnReleaseNicFlow.callReleaseSdnNics and
VmDetachNicFlow.delegate to that new method; ensure the helper uses the same
AfterAllocateSdnNicExtensionPoint iteration,
Completion/While/WhileDoneCompletion behavior, and logging semantics so callers
simply forward nics and completion to the consolidated routine.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: d0ce3bb6-1724-4686-b6d8-7b6016739e7e

📥 Commits

Reviewing files that changed from the base of the PR and between 7998ea6 and 4baf00d.

📒 Files selected for processing (3)
  • compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java
  • compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java
✅ Files skipped from review due to trivial changes (1)
  • compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java

<fix>[l2]: skip ZNS L2 in attach interceptors

See merge request zstackio/zstack!9589
Handle blank or omitted type in APIChangeL2NetworkVlanIdMsg by
normalizing to the current L2 type before validation checks.

Resolves: ZSTAC-2074

Change-Id: Icf960d0766b726047d8613305042cfa14e120c61
<fix>[network]: normalize ZNS L2 change target type handling

See merge request zstackio/zstack!9613
@MatheMatrix MatheMatrix force-pushed the sync/shixin.ruan/shixin.ruan-zcf-2047@@2 branch from 4baf00d to c34e420 Compare April 14, 2026 12:18
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java (1)

157-252: ⚠️ Potential issue | 🟠 Major

Geneve 类型缺少后续验证逻辑。

当前代码在第160行允许 L2_GENEVE_NETWORK_TYPE 通过初始检查,但后续只有 targetIsVlantargetIsNoVlan 的验证分支(分别在167-212行和213-251行),targetIsGeneve 分支完全缺失。这意味着 Geneve 类型的网络变更请求会跳过所有冲突检测和参数验证,直接通过拦截器。

虽然定义了相关错误码(如 ORG_ZSTACK_NETWORK_ZNS_10014 "Geneve type can only change VNI, not L2 type" 等),但这些错误码在代码中并未被使用。需要补充 Geneve 类型的完整验证逻辑,或明确说明 Geneve 的验证由其他拦截器(如 ZNS 模块的拦截器)单独处理。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java`
around lines 157 - 252, The interceptor currently allows targetIsGeneve to pass
initial type check but lacks a corresponding validation branch; add a branch for
targetIsGeneve in the ChangeL2NetworkVlanId handling (near the existing
targetIsVlan / targetIsNoVlan branches) that enforces Geneve-specific rules:
require/validate VNI (use msg.getVni or the proper VNI accessor), forbid
changing L2 type where applicable, run the same cluster/physical-interface
conflict checks as other branches if relevant, and throw the appropriate
ApiMessageInterceptionException using the existing error codes (e.g.
ORG_ZSTACK_NETWORK_ZNS_10014 or other defined codes) when validation fails;
alternatively, if Geneve validation is delegated to ZNS module, add a clear
comment and an explicit no-op branch for targetIsGeneve referencing that
delegation so the behavior is explicit.
🧹 Nitpick comments (6)
compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java (2)

40-43: 未使用的注入字段 vmMgr

vmMgr 字段已通过 @Autowired 注入,但在整个文件中从未被引用。这是无用代码,应当移除。

♻️ 建议移除未使用的字段
 `@Autowired`
 protected VmInstanceDeletionPolicyManager deletionPolicyMgr;
-@Autowired
-protected VmInstanceManager vmMgr;
 `@Autowired`
 protected PluginRegistry pluginRgty;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java`
around lines 40 - 43, The injected field VmInstanceManager vmMgr in class
VmReturnReleaseNicFlow is never used; remove the unused `@Autowired` field
declaration (VmInstanceManager vmMgr) from VmReturnReleaseNicFlow and also
remove any now-unused import of VmInstanceManager if present to keep the code
clean.

120-146: callReleaseSdnNics 方法与 VmDetachNicFlow 存在重复代码

该方法的实现与 VmDetachNicFlow.java 中的 callReleaseSdnNics() 几乎完全相同。建议将此公共逻辑抽取到 VmInstanceManager 或创建一个共享的工具类,以符合 DRY 原则并便于后续维护。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java`
around lines 120 - 146, VmReturnReleaseNicFlow.callReleaseSdnNics duplicates
logic found in VmDetachNicFlow.callReleaseSdnNics; extract the shared iteration
over AfterAllocateSdnNicExtensionPoint and its Completion handling into a single
reusable method (either a new static helper in a shared utility class or an
instance method on VmInstanceManager) and replace both
VmReturnReleaseNicFlow.callReleaseSdnNics and VmDetachNicFlow.callReleaseSdnNics
to delegate to that new method; ensure the new method signature accepts
List<VmNicInventory> and a Completion and preserves the existing behavior (early
return when extensions or nics are empty, warn-on-fail, call
completion.success() on done).
compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java (3)

101-101: 建议使用 import 而非内联全限定类名

java.util.Collections.singletonList(nic) 使用了全限定类名。建议在文件头部添加 import java.util.Collections;,然后直接使用 Collections.singletonList(nic),以保持代码风格一致性。

♻️ 建议的修改

在文件头部添加 import:

import java.util.Collections;

然后修改调用处:

-callReleaseSdnNics(java.util.Collections.singletonList(nic), new Completion(trigger) {
+callReleaseSdnNics(Collections.singletonList(nic), new Completion(trigger) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java` at line
101, 在 callReleaseSdnNics 调用处使用了全限定名
java.util.Collections.singletonList(nic);请在文件顶部添加 import java.util.Collections;
并将该调用替换为 Collections.singletonList(nic) 以保持项目的导入风格一致并提高可读性;定位符参考:方法调用
callReleaseSdnNics(...) 和参数 singletonList 的替换位置。

44-47: 未使用的注入字段 vmMgr

VmReturnReleaseNicFlow.java 相同,vmMgr 字段已注入但从未在此文件中使用。建议移除。

♻️ 建议移除未使用的字段
 `@Autowired`
 private VmInstanceDeviceManager vidm;
-@Autowired
-private VmInstanceManager vmMgr;
 `@Autowired`
 private PluginRegistry pluginRgty;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java` around
lines 44 - 47, 类 VmDetachNicFlow 中注入的字段 vmMgr 未被使用,请删除该字段声明(private
VmInstanceManager vmMgr;)及其相关注入注解,确保同时移除任何不再需要的导入或引用,以保持类只保留实际使用的注入(例如
PluginRegistry pluginRgty);保存并运行编译/静态检查以确认没有残留引用。

120-146: 重复的 callReleaseSdnNics 实现

此方法与 VmReturnReleaseNicFlow.java 中的实现高度重复。如前所述,建议将此逻辑抽取到共享位置(如 VmInstanceManager 或工具类),避免重复维护。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java` around
lines 120 - 146, The callReleaseSdnNics implementation in VmDetachNicFlow is
duplicated from VmReturnReleaseNicFlow; extract the shared logic that queries
pluginRgty for AfterAllocateSdnNicExtensionPoint and runs the While<> loop into
a single reusable method (e.g., VmInstanceManager.releaseSdnNics or a
SdnNicHelper.releaseSdnNics) that accepts List<VmNicInventory> and Completion,
move the While<>/ext.releaseSdnNics handling there, and replace the duplicate
implementations in VmDetachNicFlow and VmReturnReleaseNicFlow with calls to the
new shared method (keeping the same error handling behavior and use of
pluginRgty and AfterAllocateSdnNicExtensionPoint).
network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java (1)

4-4: 未使用的导入。

org.apache.poi.util.StringUtil 未在代码中使用,只使用了 org.apache.commons.lang.StringUtils。建议移除此导入。

♻️ 移除未使用的导入
-import org.apache.poi.util.StringUtil;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java` at
line 4, Remove the unused import org.apache.poi.util.StringUtil from
L2NetworkApiInterceptor (the class referenced is L2NetworkApiInterceptor.java) —
the code already uses org.apache.commons.lang.StringUtils, so delete the unused
org.apache.poi.util.StringUtil import to clean up imports and avoid warnings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java`:
- Line 84: The call to dbf.findByUuid(nic.getUuid(), VmNicVO.class) in
VmReturnReleaseNicFlow can return null when a NIC was already deleted; avoid
adding nulls to nicsToDelete and prevent NPEs by checking the result (VmNicVO)
before adding it to nicsToDelete, e.g., only add when non-null, or filter the
list prior to calling nicsToDelete.forEach(dbf::remove); ensure references to
dbf.remove and nicsToDelete are protected by this null check.

---

Outside diff comments:
In `@network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java`:
- Around line 157-252: The interceptor currently allows targetIsGeneve to pass
initial type check but lacks a corresponding validation branch; add a branch for
targetIsGeneve in the ChangeL2NetworkVlanId handling (near the existing
targetIsVlan / targetIsNoVlan branches) that enforces Geneve-specific rules:
require/validate VNI (use msg.getVni or the proper VNI accessor), forbid
changing L2 type where applicable, run the same cluster/physical-interface
conflict checks as other branches if relevant, and throw the appropriate
ApiMessageInterceptionException using the existing error codes (e.g.
ORG_ZSTACK_NETWORK_ZNS_10014 or other defined codes) when validation fails;
alternatively, if Geneve validation is delegated to ZNS module, add a clear
comment and an explicit no-op branch for targetIsGeneve referencing that
delegation so the behavior is explicit.

---

Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java`:
- Line 101: 在 callReleaseSdnNics 调用处使用了全限定名
java.util.Collections.singletonList(nic);请在文件顶部添加 import java.util.Collections;
并将该调用替换为 Collections.singletonList(nic) 以保持项目的导入风格一致并提高可读性;定位符参考:方法调用
callReleaseSdnNics(...) 和参数 singletonList 的替换位置。
- Around line 44-47: 类 VmDetachNicFlow 中注入的字段 vmMgr 未被使用,请删除该字段声明(private
VmInstanceManager vmMgr;)及其相关注入注解,确保同时移除任何不再需要的导入或引用,以保持类只保留实际使用的注入(例如
PluginRegistry pluginRgty);保存并运行编译/静态检查以确认没有残留引用。
- Around line 120-146: The callReleaseSdnNics implementation in VmDetachNicFlow
is duplicated from VmReturnReleaseNicFlow; extract the shared logic that queries
pluginRgty for AfterAllocateSdnNicExtensionPoint and runs the While<> loop into
a single reusable method (e.g., VmInstanceManager.releaseSdnNics or a
SdnNicHelper.releaseSdnNics) that accepts List<VmNicInventory> and Completion,
move the While<>/ext.releaseSdnNics handling there, and replace the duplicate
implementations in VmDetachNicFlow and VmReturnReleaseNicFlow with calls to the
new shared method (keeping the same error handling behavior and use of
pluginRgty and AfterAllocateSdnNicExtensionPoint).

In `@compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java`:
- Around line 40-43: The injected field VmInstanceManager vmMgr in class
VmReturnReleaseNicFlow is never used; remove the unused `@Autowired` field
declaration (VmInstanceManager vmMgr) from VmReturnReleaseNicFlow and also
remove any now-unused import of VmInstanceManager if present to keep the code
clean.
- Around line 120-146: VmReturnReleaseNicFlow.callReleaseSdnNics duplicates
logic found in VmDetachNicFlow.callReleaseSdnNics; extract the shared iteration
over AfterAllocateSdnNicExtensionPoint and its Completion handling into a single
reusable method (either a new static helper in a shared utility class or an
instance method on VmInstanceManager) and replace both
VmReturnReleaseNicFlow.callReleaseSdnNics and VmDetachNicFlow.callReleaseSdnNics
to delegate to that new method; ensure the new method signature accepts
List<VmNicInventory> and a Completion and preserves the existing behavior (early
return when extensions or nics are empty, warn-on-fail, call
completion.success() on done).

In `@network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java`:
- Line 4: Remove the unused import org.apache.poi.util.StringUtil from
L2NetworkApiInterceptor (the class referenced is L2NetworkApiInterceptor.java) —
the code already uses org.apache.commons.lang.StringUtils, so delete the unused
org.apache.poi.util.StringUtil import to clean up imports and avoid warnings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: 0b838d47-3fd8-4254-9f0c-5680937b3303

📥 Commits

Reviewing files that changed from the base of the PR and between 4baf00d and c34e420.

📒 Files selected for processing (10)
  • compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java
  • compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java
  • network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java
  • network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java
  • network/src/main/java/org/zstack/network/l2/L2NetworkHostHelper.java
  • network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java
  • plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanNetworkCheckerImpl.java
  • utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java
✅ Files skipped from review due to trivial changes (2)
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java
  • compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java

List<VmNicInventory> releasedNics = new ArrayList<>();
List<VmNicVO> nicsToDelete = new ArrayList<>();
for (VmNicInventory nic : spec.getVmInventory().getVmNics()) {
VmNicVO vo = dbf.findByUuid(nic.getUuid(), VmNicVO.class);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

dbf.findByUuid 可能返回 null

当 NIC 已被删除或不存在时,dbf.findByUuid(nic.getUuid(), VmNicVO.class) 会返回 null。此 null 值会被添加到 nicsToDelete 列表中,随后在 nicsToDelete.forEach(dbf::remove) 时可能导致 NPE 或不可预期的行为。

🛡️ 建议添加空值检查
 for (VmNicInventory nic : spec.getVmInventory().getVmNics()) {
     VmNicVO vo = dbf.findByUuid(nic.getUuid(), VmNicVO.class);
+    if (vo == null) {
+        releasedNics.add(nic);
+        continue;
+    }
     if (VmInstanceConstant.USER_VM_TYPE.equals(spec.getVmInventory().getType())) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java` at
line 84, The call to dbf.findByUuid(nic.getUuid(), VmNicVO.class) in
VmReturnReleaseNicFlow can return null when a NIC was already deleted; avoid
adding nulls to nicsToDelete and prevent NPEs by checking the result (VmNicVO)
before adding it to nicsToDelete, e.g., only add when non-null, or filter the
list prior to calling nicsToDelete.forEach(dbf::remove); ensure references to
dbf.remove and nicsToDelete are protected by this null check.

Release SDN NICs before removing VmNicVO.
Keep NIC deletion on release failure.

Resolves: ZCF-2047

Change-Id: I83f534ea19849467a728e3b6fb9ee2f6bb43bb7e
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MatheMatrix MatheMatrix force-pushed the sync/shixin.ruan/shixin.ruan-zcf-2047@@2 branch from c34e420 to 66c8e13 Compare April 15, 2026 05:47
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java (1)

84-113: ⚠️ Potential issue | 🟠 Major

先过滤掉已不存在的 VmNicVO

Line 84 的 dbf.findByUuid(...) 可能返回 null。当前代码在非 Direct 分支会直接解引用 vo,其余分支则会把 null 放进 nicsToDelete,随后在 Line 105 / Line 112 调 dbf.remove。L2 NIC 若已先被卸载,这里会把回收流程打成 NPE。

建议修改
                 for (VmNicInventory nic : spec.getVmInventory().getVmNics()) {
                     VmNicVO vo = dbf.findByUuid(nic.getUuid(), VmNicVO.class);
+                    if (vo == null) {
+                        releasedNics.add(nic);
+                        continue;
+                    }
+
                     if (VmInstanceConstant.USER_VM_TYPE.equals(spec.getVmInventory().getType())) {
                         VmInstanceDeletionPolicy deletionPolicy = getDeletionPolicy(spec, data);
                         if (deletionPolicy == VmInstanceDeletionPolicy.Direct) {
                             nicsToDelete.add(vo);
                         } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java`
around lines 84 - 113, The code in VmReturnReleaseNicFlow calls
dbf.findByUuid(...) which can return null; avoid NPE by filtering out missing
VmNicVOs before using or adding them to nicsToDelete/releasedNics: when
dbf.findByUuid(nic.getUuid(), VmNicVO.class) returns null, skip that nic
(optionally log a warning) and do not add null to nicsToDelete or releasedNics,
and ensure the later callSite that removes entries
(nicsToDelete.forEach(dbf::remove)) only iterates real VmNicVO instances; update
the loop that builds nicsToDelete/releasedNics around callReleaseSdnNics
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java`:
- Around line 127-143: In VmDetachNicFlow, the releaseSdnNics call inside the
new While<>(exts).each(...) swallows failures (logger.warn and always
wcomp.done()) and the enclosing WhileDoneCompletion always calls
completion.success(), allowing NIC DB deletion to proceed; change the failure
handling in the Completion passed to ext.releaseSdnNics so that on
fail(ErrorCode) you propagate the error to the outer completion (e.g.,
collect/forward the ErrorCode into the WhileDoneCompletion and call
completion.fail(...)) instead of only logging, or alternatively persist a
compensation/retry record before allowing completion.success() to proceed;
update the Completion.fail path and the WhileDoneCompletion.done(...) logic to
ensure failures from releaseSdnNics prevent unconditional completion.success()
and trigger retry/compensation handling.

In `@compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java`:
- Around line 127-143: In VmReturnReleaseNicFlow, the ext.releaseSdnNics(...)
fail handler currently only logs and calls wcomp.done(), and the outer
WhileDoneCompletion always calls completion.success(), which lets callers delete
local VmNicVO even when SDN release failed; change the flow to propagate errors:
collect ErrorCodes from ext.releaseSdnNics within the While (or record failures
in a shared ErrorCodeList), and in WhileDoneCompletion.done inspect that list
and call completion.fail(...) with a composed ErrorCode when any SDN release
failed (or alternatively enqueue a compensating retry task before returning
success); ensure changes touch the ext.releaseSdnNics fail path, the
WhileDoneCompletion.done logic that currently calls completion.success(), and
the completion passed into VmReturnReleaseNicFlow so the caller receives
failures instead of silent success.

---

Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java`:
- Around line 84-113: The code in VmReturnReleaseNicFlow calls
dbf.findByUuid(...) which can return null; avoid NPE by filtering out missing
VmNicVOs before using or adding them to nicsToDelete/releasedNics: when
dbf.findByUuid(nic.getUuid(), VmNicVO.class) returns null, skip that nic
(optionally log a warning) and do not add null to nicsToDelete or releasedNics,
and ensure the later callSite that removes entries
(nicsToDelete.forEach(dbf::remove)) only iterates real VmNicVO instances; update
the loop that builds nicsToDelete/releasedNics around callReleaseSdnNics
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: 78687c16-4e2f-4068-a339-f5b02be6d92a

📥 Commits

Reviewing files that changed from the base of the PR and between c34e420 and 66c8e13.

📒 Files selected for processing (6)
  • compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java
  • compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java
  • network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java
  • network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java
  • utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java
  • network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java
  • compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java
  • utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java

Comment on lines +127 to +143
new While<>(exts).each((ext, wcomp) -> {
ext.releaseSdnNics(nics, new Completion(wcomp) {
@Override
public void success() {
wcomp.done();
}

@Override
public void fail(ErrorCode errorCode) {
logger.warn(String.format("releaseSdnNics extension failed: %s, continue", errorCode));
wcomp.done();
}
});
}).run(new WhileDoneCompletion(completion) {
@Override
public void done(ErrorCodeList errorCodeList) {
completion.success();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

这里同样把 SDN 释放失败吞掉了。

ext.releaseSdnNics(...) 失败后只会 warn 并继续,Line 143 再统一走 completion.success();于是调用方仍会在 Line 104 / Line 112 删除 NIC 记录并 trigger.next()。如果外部 SDN 控制器释放失败,这里会直接丢失本地重试入口,容易留下孤儿 SDN 资源。建议改成失败透传,或者在删除数据库记录前先持久化补偿/重试信息。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmDetachNicFlow.java` around
lines 127 - 143, In VmDetachNicFlow, the releaseSdnNics call inside the new
While<>(exts).each(...) swallows failures (logger.warn and always wcomp.done())
and the enclosing WhileDoneCompletion always calls completion.success(),
allowing NIC DB deletion to proceed; change the failure handling in the
Completion passed to ext.releaseSdnNics so that on fail(ErrorCode) you propagate
the error to the outer completion (e.g., collect/forward the ErrorCode into the
WhileDoneCompletion and call completion.fail(...)) instead of only logging, or
alternatively persist a compensation/retry record before allowing
completion.success() to proceed; update the Completion.fail path and the
WhileDoneCompletion.done(...) logic to ensure failures from releaseSdnNics
prevent unconditional completion.success() and trigger retry/compensation
handling.

Comment on lines +127 to +143
new While<>(exts).each((ext, wcomp) -> {
ext.releaseSdnNics(nics, new Completion(wcomp) {
@Override
public void success() {
wcomp.done();
}

@Override
public void fail(ErrorCode errorCode) {
logger.warn(String.format("releaseSdnNics extension failed: %s, continue", errorCode));
wcomp.done();
}
});
}).run(new WhileDoneCompletion(completion) {
@Override
public void done(ErrorCodeList errorCodeList) {
completion.success();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

不要在 SDN 释放失败后仍然无条件删除本地 NIC。

这里对 ext.releaseSdnNics(...)fail() 只记日志并 wcomp.done(),Line 143 又无条件 completion.success()。结果是外部 SDN 清理失败时,调用方仍会继续删除/清空本地 VmNicVO,云平台和 SDN 控制器状态会分叉,而且没有重试抓手。建议至少把失败透传给调用方,或在继续删本地状态前先落一条可重试的补偿任务。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java`
around lines 127 - 143, In VmReturnReleaseNicFlow, the ext.releaseSdnNics(...)
fail handler currently only logs and calls wcomp.done(), and the outer
WhileDoneCompletion always calls completion.success(), which lets callers delete
local VmNicVO even when SDN release failed; change the flow to propagate errors:
collect ErrorCodes from ext.releaseSdnNics within the While (or record failures
in a shared ErrorCodeList), and in WhileDoneCompletion.done inspect that list
and call completion.fail(...) with a composed ErrorCode when any SDN release
failed (or alternatively enqueue a compensating retry task before returning
success); ensure changes touch the ext.releaseSdnNics fail path, the
WhileDoneCompletion.done logic that currently calls completion.success(), and
the completion passed into VmReturnReleaseNicFlow so the caller receives
failures instead of silent success.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants